google-map-react Autocomplete


Posted by Rich on 2021-09-04

發現錯誤 請勿參考本文內容 待後續修改

終於把 debounce 的功能加到搜尋欄裡面了!當初一度要放棄,先用按鈕做代替。後來突破盲腸,終於做出有 autocomplete 的搜尋列。在 react 裡面實作這項功能對我這個萌新而言,比較複雜。而且很多作法是從來沒想過的。
第一個沒想到的事,我以為會直接用 useState 去控制輸入進搜尋列的值。像這樣:

const [inputValue, setInputValue] = useState("")

<input value={inputValue} onChange={setInputValue} />

但實際根據 Clay 助教的寫法會是這樣:

let input = useRef("")
const [inputText, setInputText] = useState("")

<input ref={input} onChange={() => {
    setInputText(input.current.value)
}}

我蠻好奇這種寫法除了 debounce 以外的其他使用時機。至於為什麼要這樣寫? 看到後面應該就會知道了。
最後再配上這個:

useEffect(() => {
callapi()
},[inputText])

就可以在 inputText 改變的時候去 callapi。
這樣也可以跑,但就是每次輸入改變的時候,都會 call api。我們當然不希望這樣(尤其當你是 google api 的免費仔,call 的數量有限)。
在這之前還是先試試看 react 裡面可不可以好好 debounce。我去載了 lodash 這個套件,裡面有 debounce 這個方法讓我們輕鬆 debounce。

import _ from "lodash";
const debounced = _.debounce(??,1000)

這時候我卡住了,我一開始去 debounce callapi 的部份,很直覺的想法,因為我只想送出一個 request。但這樣有兩個問題。

  • 第一個 react 裡面用 debounce 要用 useCallback 這個 hook。原因的話,我自己的理解是,每次 react 渲染頁面都會重新呼叫一次函式。那我們在函式裡面的 const debounced = _.debounce(??) 也會被重新宣告一次。那這樣上一次呼叫的 debounced 和這次呼叫的 debounced 就不會是同個函式。 如果大概了解過 debounce 就知道。我們應該是呼叫同個函式多次才會得到 debounce 的效果。所以變成這樣:

    const debounced = useCallback(_.debounce(??,1000),??)
    

    useCallback 的第二個參數是,當這個參數改變的時候,會回傳不同的函式。當初我耍蠢放進 [inputText],然後就沒有 debounce 了。這裡應該會希望不管怎樣都會傳同樣的函式,所以放 []

    const debounced = useCallback(_.debounce(??,1000),[])
    
  • 第二個問題是要 debounce 什麼,前面説到是這樣

    const debounced = useCallback(_.debounce(callapi(),1000),[])
    

    看起來合理,等一秒沒打字再 callapi。那要什麼時候呼叫 debounced? 前面的 useEffect 的 callapi 變成 debounced。所以是 inputText 改變的時候,就呼叫 debounced。

    useEffect(() => {
    debounced()
    },[inputText])
    

    但我用了之後發現有個問題。debounced 回傳的函式都是一樣的。「咦?前面才說要一樣啊」一樣的話代表 callapi() 裡面的參數也是一樣的。我們這次要做的是 autocomplete 的效果。所以隨著 inputText 改變,callapi 裡面的參數也要改變(要戴上使用者輸入去搜尋)。但現在放進 useCallback 裡面我不會改了。輾轉到最後,就用 Clay 教學裡面的方法。

就是 debounce 的不是 callapi ,是 setInputText

let input = useRef("")
import _ from "lodash";
const [inputText, setInputText] = useState("")

<input ref = {input}  onChange={() => {
debounced()
}}/>
const debounced = useCallback(_.debounce(setInputText(input.current.value),1000),[])

useEffect(() => {
callAPI()
},[inputText])

每次 input 的值改變的時候都會呼叫 debounced ,這時候真正被 debounce 的是 setInputText。所以在打字停止一秒這之前,inputText 都維持初始值,沒有改變。停止打字一秒後,inputText 才真正被改變。而被改變時,也觸發 useEffect,所以去 callapi。真是漂亮的設計!
結果長這樣:

有任何錯誤歡迎指正,感謝觀賞。

參考的教學










Related Posts

Fake Vapes and How to Avoid Them

Fake Vapes and How to Avoid Them

部署 (1) —— 建立 AWS EC2 主機及 SSH 連線

部署 (1) —— 建立 AWS EC2 主機及 SSH 連線

[Day03]: 容器與映像技術原理

[Day03]: 容器與映像技術原理


Comments